package com.example.scribe.backend;

import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackManipulation.AbstractBase;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.constant.TextConstant;
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;

public final class BytecodeOps {
    private BytecodeOps() {}

    public static StackManipulation pushInt(int value) {
        return IntegerConstant.forValue(value);
    }

    public static StackManipulation pushText(String value) {
        return new TextConstant(value);
    }

    public static StackManipulation loadInt(int index) {
        return MethodVariableAccess.INTEGER.loadFrom(index);
    }

    public static StackManipulation storeInt(int index) {
        return MethodVariableAccess.INTEGER.storeAt(index);
    }

    public static StackManipulation iadd() {
        return new Insn(Opcodes.IADD);
    }

    public static StackManipulation isub() {
        return new Insn(Opcodes.ISUB);
    }

    public static StackManipulation imul() {
        return new Insn(Opcodes.IMUL);
    }

    public static StackManipulation idiv() {
        return new Insn(Opcodes.IDIV);
    }

    public static StackManipulation irem() {
        return new Insn(Opcodes.IREM);
    }

    // Bitwise operations
    public static StackManipulation iand() {
        return new Insn(Opcodes.IAND);
    }

    public static StackManipulation ior() {
        return new Insn(Opcodes.IOR);
    }

    public static StackManipulation ixor() {
        return new Insn(Opcodes.IXOR);
    }

    public static StackManipulation ineg() {
        return new Insn(Opcodes.INEG);
    }

    public static StackManipulation ishl() {
        return new Insn(Opcodes.ISHL);
    }

    public static StackManipulation ishr() {
        return new Insn(Opcodes.ISHR);
    }

    public static StackManipulation iushr() {
        return new Insn(Opcodes.IUSHR);
    }

    public static StackManipulation ireturn() {
        return MethodReturn.INTEGER;
    }

    public static StackManipulation invokeStatic(java.lang.reflect.Method method) {
        return MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(method));
    }

    public static StackManipulation invokeStaticInternal(String ownerInternalName, String methodName, String descriptor) {
        return new AbstractBase() {
            @Override
            public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, ownerInternalName, methodName, descriptor, false);
                return Size.ZERO;
            }
        };
    }

    public static String descriptorForIntArgs(int numArgs) {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        for (int i = 0; i < numArgs; i++) sb.append('I');
        sb.append(')').append('I');
        return sb.toString();
    }

    private static final class Insn extends AbstractBase {
        private final int opcode;
        Insn(int opcode) { this.opcode = opcode; }
        @Override
        public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
            methodVisitor.visitInsn(opcode);
            return Size.ZERO;
        }
    }
}


